Skip to content

feat(EVES-009): new draft about evidence#38

Open
flhps wants to merge 4 commits into
mainfrom
feat/eves-009
Open

feat(EVES-009): new draft about evidence#38
flhps wants to merge 4 commits into
mainfrom
feat/eves-009

Conversation

@flhps
Copy link
Copy Markdown
Contributor

@flhps flhps commented Mar 18, 2026

No description provided.

Signed-off-by: felix hoops <9974641+flhps@users.noreply.github.com>
@flhps flhps requested a review from jdsika March 18, 2026 11:14
@flhps flhps changed the title feat(EVES-009): new draft on evidence feat(EVES-009): new draft about evidence Mar 18, 2026
jdsika added a commit that referenced this pull request Apr 2, 2026
Resolve all review findings against PR #24 (simpulse-id-credentials):

- Replace did:web with did:ethr on Base (ERC-1056) throughout
- Fix all JSON-LD context URLs to w3id.org persistent identifiers
- Fix ontology/schema ID to w3id.org/ascs-ev/simpulse-id/core/v1
- Fix DID verification methods: P-256 JsonWebKey (not Tezos/Etherlink)
- Fix reference implementation URL and repository structure
- Fix does:web typo and all factual mismatches

Add missing specification content:
- Section 3.4: Credential Subject Semantics (member vs memberOf)
- Section 3.5: Revocation (harbour:CRSetEntry)
- Expanded Lifecycle with issuance order and prerequisites
- Schema-first (LinkML) architecture documented
- SHACL validation requirement added

EVES-001 format compliance:
- RFC 2119 keywords (MUST/SHOULD/MAY) throughout normative sections
- Proper linked references with URLs
- Type changed from Process to Standards
- discussions-to points to EVES repo issues
- EVES-009 added as dependency (evidence protocol)

Evidence details deferred to EVES-009 (PR #38).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@jdsika jdsika left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implementation Review: harbour-credentials & simpulse-id-credentials

We audited the harbour-credentials and simpulse-id-credentials implementations against this specification. Overall the implementation is substantially compliant, but the challenge binding mechanism diverges from §2/§4.2 for good reason. Below are detailed findings.

Challenge Binding: OID4VP vs EVES-009

EVES-009 §2 says:

The challenge is the cryptographic hash of the serialized message. The signature object is a VP where the challenge is embedded in the VP's nonce or challenge field.

EVES-009 §4.2 check 4 says:

The VP's nonce MUST equal hash(message).

What harbour implements instead:
harbour uses the OID4VP dual-binding approach with SD-JWT VPs:

  1. VP nonce = random hex string (replay prevention)
  2. KB-JWT transaction_data_hashes = [SHA-256(base64url(transaction_data_param))] per OID4VP Appendix B.3.3

This means the message binding is done via the KB-JWT hash chain, not via the VP nonce directly. The VP nonce serves purely for replay prevention.

Why this divergence is justified:

  1. OID4VP compatibility: The wallet ecosystem (e.g., EUDI, Sphereon, walt.id) implements OID4VP's transaction_data parameter and expects KB-JWT binding — not a custom nonce format. Aligning with EVES-009's nonce = hash(message) would break interoperability with standard OID4VP wallets.

  2. Separation of concerns: OID4VP separates replay prevention (nonce) from message binding (transaction_data_hashes). Overloading nonce with the message hash conflates these two distinct security properties.

  3. Multiple evidence items: harbour supports multiple DelegatedSignatureEvidence items in a single VP. The KB-JWT transaction_data_hashes array supports this naturally. A single nonce field cannot bind to multiple distinct messages.

  4. SD-JWT-VC specifics: For SD-JWT VPs (RFC 9901), the KB-JWT already carries sd_hash binding the VP to the disclosed credential. Adding transaction_data_hashes extends this pattern consistently. Putting the hash in nonce instead would be inconsistent with the SD-JWT verification model.

Suggestion for the spec: Consider relaxing §4.2 check 4 to allow implementation-specific binding mechanisms, e.g.:

The VP MUST be bound to the message. This binding MAY be achieved by setting the VP's nonce equal to hash(message), or by using the OID4VP transaction_data mechanism (Appendix B.3.3) where the KB-JWT transaction_data_hashes array contains hash(transaction_data_param).

This would accommodate both the simple nonce-based approach (useful for non-SD-JWT VPs) and the OID4VP KB-JWT approach (required for SD-JWT VPs and wallet interoperability).

Other Compliance Findings

Requirement Status Notes
Evidence = proof of consent TransactionData + VP with delegation evidence
Message with description, nonce, timestamp TransactionData.create() with SIWE-style display
Challenge = SHA-256(serialize(message)) compute_hash() with canonical JSON
VP signature verified vs Holder DID verify_sd_jwt_vp() step 1
Each VC independently verified Step 2 (issuer JWT signature)
SD-JWT VCs RECOMMENDED Full selective disclosure support
Replay prevention (unique nonce) secrets.token_hex(4) per transaction
Time-bounding validate_transaction_data() with configurable max_age
Human-readable consent display render_transaction_display() (SIWE-inspired)
Credential freshness / revocation ⚠️ Not enforced in VP verification — caller responsibility

Minor Spec Feedback

  1. §3.2 step 1 has a typo: "challenge derived form it" → "from it"
  2. §3.2 step 3 references "credentail request" → "credential request" (in mermaid diagram)
  3. §1 message format: The spec says implementations SHOULD include "a domain or origin identifying the Requester". harbour's TransactionData includes this implicitly via credential_ids (DIDs identifying the credential being acted upon) but does not have an explicit origin field. Consider whether DID-based identification of the Requester satisfies this requirement.

jdsika added a commit that referenced this pull request Apr 4, 2026
* Add first draft for EVES-008
* mardown lint

Resolve all review findings against PR #24 (simpulse-id-credentials):

- Replace did:web with did:ethr on Base (ERC-1056) throughout
- Fix all JSON-LD context URLs to w3id.org persistent identifiers
- Fix ontology/schema ID to w3id.org/ascs-ev/simpulse-id/core/v1
- Fix DID verification methods: P-256 JsonWebKey (not Tezos/Etherlink)
- Fix reference implementation URL and repository structure
- Fix does:web typo and all factual mismatches

Add missing specification content:
- Section 3.4: Credential Subject Semantics (member vs memberOf)
- Section 3.5: Revocation (harbour:CRSetEntry)
- Expanded Lifecycle with issuance order and prerequisites
- Schema-first (LinkML) architecture documented
- SHACL validation requirement added

EVES-001 format compliance:
- RFC 2119 keywords (MUST/SHOULD/MAY) throughout normative sections
- Proper linked references with URLs
- Type changed from Process to Standards
- discussions-to points to EVES repo issues
- EVES-009 added as dependency (evidence protocol)

Evidence details deferred to EVES-009 (PR #38).

* docs(eves-008): clarify context vs schema URL path difference

Review finding: the /v1/ context path vs /core/v1 schema path
was confusing without explanation. Add clarifying note.

* fix(EVES-008): align spec with reference implementation

- Fix subject class URI: simpulseid:Participant → simpulseid:OrganizationParticipant
  (renamed to avoid JSON-LD context collision with gx:Participant)
- Add SimpulseIdLegalForm enum table (21 values across DE/US/UK jurisdictions)
- Add DID document structure requirements: signing vs non-signing DID
  invariants (verificationMethod, authentication, assertionMethod, controller)
- Add concrete @context example for credential JSON-LD
- Add trailing newline
---------

Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
ClemensLinnhoff pushed a commit that referenced this pull request Apr 4, 2026
* Add first draft for EVES-008
* mardown lint

Resolve all review findings against PR #24 (simpulse-id-credentials):

- Replace did:web with did:ethr on Base (ERC-1056) throughout
- Fix all JSON-LD context URLs to w3id.org persistent identifiers
- Fix ontology/schema ID to w3id.org/ascs-ev/simpulse-id/core/v1
- Fix DID verification methods: P-256 JsonWebKey (not Tezos/Etherlink)
- Fix reference implementation URL and repository structure
- Fix does:web typo and all factual mismatches

Add missing specification content:
- Section 3.4: Credential Subject Semantics (member vs memberOf)
- Section 3.5: Revocation (harbour:CRSetEntry)
- Expanded Lifecycle with issuance order and prerequisites
- Schema-first (LinkML) architecture documented
- SHACL validation requirement added

EVES-001 format compliance:
- RFC 2119 keywords (MUST/SHOULD/MAY) throughout normative sections
- Proper linked references with URLs
- Type changed from Process to Standards
- discussions-to points to EVES repo issues
- EVES-009 added as dependency (evidence protocol)

Evidence details deferred to EVES-009 (PR #38).

* docs(eves-008): clarify context vs schema URL path difference

Review finding: the /v1/ context path vs /core/v1 schema path
was confusing without explanation. Add clarifying note.

* fix(EVES-008): align spec with reference implementation

- Fix subject class URI: simpulseid:Participant → simpulseid:OrganizationParticipant
  (renamed to avoid JSON-LD context collision with gx:Participant)
- Add SimpulseIdLegalForm enum table (21 values across DE/US/UK jurisdictions)
- Add DID document structure requirements: signing vs non-signing DID
  invariants (verificationMethod, authentication, assertionMethod, controller)
- Add concrete @context example for credential JSON-LD
- Add trailing newline
---------

Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
@jdsika
Copy link
Copy Markdown
Contributor

jdsika commented Apr 4, 2026

Follow-up Review: Additional Findings from Cross-Implementation Analysis

After deeper analysis of both harbour-credentials and simpulse-id-credentials against this spec, several additional gaps were identified.

Critical: Serialization Canonicalization (§2)

The spec says `SHA-256(serialize(message))` but does not define a canonical serialization. harbour-credentials uses `json.dumps(sort_keys=True, separators=(",",":"))` — Python's deterministic JSON output. Two implementations using different JSON serializers (different key ordering, whitespace, Unicode escaping) would produce different hashes for the same logical message, making challenges incompatible.

Recommendation: Reference RFC 8785 (JSON Canonicalization Scheme) or explicitly mandate sorted-key, no-whitespace JSON serialization.

Critical: Empty VPs Conflict with §2

§2 line 81 states:

"The VP contains one or more Verifiable Credentials (VCs) proving the Holder's identity and attributes."

But simpulse-id-credentials creates evidence VPs containing zero inner VCs — they prove holder consent (via nonce binding) without presenting any credentials. This is the entire evidence chain pattern: the evidence VP carries only `holder` + `nonce`, no `verifiableCredential` array.

This is semantically valid — sometimes you need proof that a specific DID holder consented, without requiring them to present identity attributes. The spec should allow zero-VC VPs:

"The VP contains zero or more Verifiable Credentials (VCs). When VCs are present, they prove the Holder's identity and attributes."

Medium: SD-JWT VP Verification Steps Missing (§4.2)

harbour-credentials implements an 8-step verification for SD-JWT VPs (`verify_sd_jwt_vp()`) including:

  • `sd_hash` binding (RFC 9901 §4.3.1)
  • KB-JWT signature verification
  • Disclosure hash matching against `_sd` digests
  • Transaction data hash verification in KB-JWT

§4.2 has only 5 checks and doesn't cover any SD-JWT-specific steps. Since SD-JWT VCs are RECOMMENDED (§2), the verification section should at minimum reference the SD-JWT VP verification requirements or defer to RFC 9901.

Medium: Multiple Evidence Items Per VP

harbour supports multiple `DelegatedSignatureEvidence` items in a single VP (validated via `transaction_data_hashes` array in KB-JWT). The spec implicitly assumes 1:1 between message and VP. Should clarify whether batching is permitted.

Style & Convention

  • Gender-neutral language (§3.2 line 110-111): "He also provides" / "his wallet" → "The Requester also provides" / "the Holder's wallet"
  • Redundant phrasing (§1 line 17): "e.g., for example" — pick one
  • Outdated reference (Adding a SHCAL, Ontology and README - file to onboardingAsset #8): `draft-ietf-oauth-sd-jwt-vc-00` is now RFC 9901
  • Implementation section (line 188): "An initial implementation is planned" — both harbour-credentials and simpulse-id-credentials already implement this spec. Should cite them as reference implementations.

Suggested Priority

  1. Challenge binding relaxation (§2, §4.2) — original review finding
  2. Empty VP allowance (§2) — reference implementation conflict
  3. Serialization canonicalization (§2) — interoperability gap
  4. Typo fixes + gender-neutral language (§3.2)
  5. SD-JWT verification reference (§4.2)
  6. Reference updates (RFC 9901, OID4VP appendix)
  7. Implementation section update

Align spec with harbour-credentials and simpulse-id-credentials
reference implementations based on cross-implementation review.

Specification changes:
- §2: Mandate canonical serialization (RFC 8785 or sorted-key
  no-whitespace JSON) for deterministic challenge hashing
- §2: Allow zero-VC VPs (pure consent proofs where Holder DID
  binding alone demonstrates authorization)
- §2: Support both nonce-based and OID4VP KB-JWT transaction_data_hashes
  binding mechanisms for challenge binding
- §2: Allow multiple evidence items per VP via hash arrays
- §4.2: Relax challenge binding check to accommodate OID4VP dual-binding
  (VP nonce for replay prevention + KB-JWT hashes for message binding)
- §4.2: Add SD-JWT-specific verification requirements (sd_hash, disclosure
  hash validation per RFC 9901)
- §4.2: Clarify VC requirement check is skipped for zero-VC VPs
- §5: Add canonical serialization as a security consideration
- §6: Add consent-only evidence privacy guidance for zero-VC VPs

Style and references:
- Fix gendered language: "He also provides" -> "The Requester also provides"
- Fix "his wallet" -> "the Holder's wallet"
- Fix "e.g., for example" -> "e.g.,"
- Fix typos: "derived form" -> "derived from", "credentail" -> "credential"
- Update ref #8 from expired draft to RFC 9901
- Add RFC 8785 (JSON Canonicalization Scheme) as ref #9
- Add OID4VP Appendix B.3.3 as ref #10
- Update Implementation section: cite harbour-credentials and
  simpulse-id-credentials as existing reference implementations

Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
jdsika added 2 commits May 18, 2026 11:51
Signed-off-by: Carlo van Driesten <carlo.van-driesten@bmw.de>

# Conflicts:
#	EVES/SUMMARY.md
#	README.md
Signed-off-by: Carlo van Driesten <carlo.van-driesten@bmw.de>
@jdsika
Copy link
Copy Markdown
Contributor

jdsika commented May 18, 2026

Independent Audit: EVES-009 vs Reference Implementations

Scope: Full cross-reference of eves-009.md (current branch head) against harbour-credentials and simpulse-id-credentials, plus reconciliation with existing reviewer comments.


Status of Previous Review Findings

The fix(EVES-009) commit successfully addressed all 7 priority items from @jdsika's review:

Finding Status
Challenge binding relaxation (OID4VP dual-binding) ✅ §2 lines 89-93, §4.2 check 4
Empty VP allowance (zero or more VCs) ✅ §2 line 85
Serialization canonicalization (RFC 8785) ✅ §2 line 81
Typos (form→from, credentail→credential) ✅ Fixed
Gender-neutral language ✅ Fixed
SD-JWT verification (sd_hash, disclosure validation) ✅ §4.2 check 2, check 4
RFC 9901 reference + implementation section ✅ §References #8, §Implementation

New Findings from Implementation Audit

🔴 HIGH: TransactionData Structure Undefined

Problem: The spec says (§1) implementations SHOULD include "description, domain/DID, nonce, timestamp" but provides no normative schema. harbour-credentials defines a TransactionData class with fields {action, nonce, iat, exp, txn}. simpulse-id-credentials extends txn with {credentialId}. Without a normative structure, two independent implementations cannot produce interoperable challenges.

Evidence: harbour's TransactionData.to_json() → canonical JSON → SHA-256. If another implementation structures the same logical message differently, the hashes will differ.

Recommendation: Add a normative JSON schema (or at minimum an informative example) in a new §2.1:

{
  "action": "credential.issue",
  "description": "Issue ASCS membership credential",
  "origin": "did:web:envited-x.net",
  "nonce": "a1b2c3d4e5f6a7b8",
  "iat": 1716024590,
  "exp": 1716024890,
  "txn": { "credentialId": "did:web:example.com:cred:123" }
}

🔴 HIGH: Composite Nonce Format Unspecified

Problem: §2 line 89 mentions <random> <action_type> <hash> as a composite format but doesn't define delimiters, field lengths, or parsing rules. harbour uses <nonce> HARBOUR_DELEGATE <sha256-hex> with space delimiters and expects exactly 3 parts via split(" ").

Risks:

  • If a nonce value contains a space, parsing fails silently
  • Different implementations may use different action type strings (HARBOUR_DELEGATE vs credential.issue vs others)
  • No interoperability between independent implementations

Recommendation: Either:

  1. Define the composite format normatively: <nonce>:<action_type>:<hash> with : delimiter and constraints (nonce = hex, no colons allowed), OR
  2. Remove the composite format from §2 and state that for Simple VPs the nonce MUST equal hash(message) (keeping it simple), with the composite format documented only as an implementation note in harbour

🟡 MEDIUM: Nonce Entropy Minimum Not Specified

Problem: harbour uses secrets.token_hex(4) = 32 bits of entropy. The spec says "unique nonce" (§1, §5) but doesn't specify minimum entropy. 32 bits provides only ~4 billion unique values — insufficient for high-throughput systems and susceptible to birthday attacks for collision-based replay.

Recommendation: Add to §5 Security Considerations:

Message nonces MUST contain at least 128 bits of entropy (for example, 16 random bytes hex-encoded to 32 characters). Implementations using shorter nonces risk replay via birthday collisions.


🟡 MEDIUM: Revocation Check Missing from §4.2 Verification Steps

Problem: §5 says "Verifiers SHOULD check the revocation status of presented VCs" but §4.2 verification steps (the normative checklist) don't include revocation. harbour-credentials confirms this gap: its verify_sd_jwt_vp() does not check revocation — it's left to the caller.

Recommendation: Add step 2b or modify step 2 in §4.2:

Credential freshness (RECOMMENDED): Verifiers SHOULD verify that each presented VC has not been revoked, using the revocation mechanism indicated in the credential (for example, Status List 2021 or OCSP).


🟡 MEDIUM: Evidence Envelope/Storage Format Undefined

Problem: simpulse-id-credentials embeds the evidence VP JWT string inside a VC's evidence array with type: harbour:CredentialEvidence. The spec says evidence is "stored" (§3.2 step 5) but never defines:

  • How evidence should be serialized for storage
  • A type URI for evidence artifacts
  • Whether the evidence VP should be wrapped in another VC or stored standalone

Recommendation: Add an informative §7 "Evidence Packaging" or expand §3.2:

Implementations MAY embed the evidence VP token inside a Verifiable Credential's evidence property (per VC Data Model §5.5) using type EvidenceVerifiablePresentation. Alternatively, the raw VP token MAY be stored standalone with associated metadata (message, timestamp, holder DID).


🟡 MEDIUM: Time-Bounding Guidance Absent

Problem: harbour enforces a 300-second (5 min) default max_age via validate_transaction_data(). The spec says "SHOULD set an expiration" (§5) but provides no guidance on reasonable bounds.

Recommendation: Add informative note to §5:

A typical maximum age for evidence requests is 5–15 minutes. Shorter windows reduce the risk of stale consent but must account for wallet interaction latency.


🟢 LOW: Action Type Registry

Problem: harbour uses HARBOUR_DELEGATE, simpulse uses credential.issue as action types. Without a registry or naming convention, action types may collide or be ambiguous.

Recommendation: Add informative note in §1:

Action types SHOULD use reverse-domain or namespaced identifiers to avoid collisions (for example, envited-x.credential.issue, envited-x.asset.list).


🟢 LOW: Error Model Undefined

Problem: The spec defines verification checks but not how implementations should report failures. harbour raises ValueError with descriptive messages. Lack of normative error conditions makes integration testing across implementations difficult.

Recommendation: Consider adding a table of normative error conditions in §4.2 (for example: INVALID_VP_SIGNATURE, CHALLENGE_MISMATCH, VC_REQUIREMENT_UNMET, HOLDER_MISMATCH).


🟢 LOW: §3.2 Mermaid Diagram Only Shows Simple VP Flow

Problem: The mermaid diagram shows nonce=challenge directly in the OID4VP request. For SD-JWT VPs (the RECOMMENDED format), the flow differs: the nonce is random and the challenge binding happens via transaction_data parameter. This could mislead implementers.

Recommendation: Either add a second diagram for the SD-JWT flow, or add a note below the diagram:

Note: For SD-JWT VPs, the nonce in the OID4VP request carries a random value. The message binding is achieved via the transaction_data request parameter and corresponding KB-JWT transaction_data_hashes.


Summary of Recommended Priority

# Finding Severity Effort
1 TransactionData schema / normative example 🔴 HIGH Medium
2 Composite nonce format specification or removal 🔴 HIGH Low
3 Nonce minimum entropy (128 bits) 🟡 MEDIUM Low
4 Revocation check in §4.2 🟡 MEDIUM Low
5 Evidence envelope/storage format guidance 🟡 MEDIUM Medium
6 Time-bounding guidance (5–15 min) 🟡 MEDIUM Low
7 Action type naming convention 🟢 LOW Low
8 Error model 🟢 LOW Medium
9 SD-JWT flow diagram or note 🟢 LOW Low

Items 1–2 are interoperability-critical. Items 3–6 are security/implementation hardening. Items 7–9 are polish.


Merge Conflict Resolution

The merge conflict (EVES-008 on main vs EVES-009 on this branch in SUMMARY.md and README.md) has been resolved. Both specs are now listed. Line-length lint violations (>300 chars) in §2 and §4.2 have also been fixed.

@jdsika
Copy link
Copy Markdown
Contributor

jdsika commented May 18, 2026

Standards-Based Solutions for EVES-009 Audit Findings

Based on analysis of the reference specs in harbour-credentials/docs/specs/references/ (OID4VP 1.0, RFC 9901, VC Data Model 2.0, Token Status List, VC-JOSE-COSE) and the internal architecture decisions (ADR-003 canonicalization, delegation-challenge-encoding spec).


Issue 1 — TransactionData Structure: Use OID4VP §8.4 transaction_data

Standards basis: OID4VP 1.0 §8.4 defines transaction_data as an OPTIONAL array of base64url-encoded JSON objects in the Authorization Request. Each object carries Verifier-defined fields describing the transaction the user is consenting to.

Solution: Align the EVES-009 message structure with OID4VP's transaction_data parameter directly. Define a normative profile:

{
  "type": "envited-x-consent/v1",
  "action": "<namespaced-action-type>",
  "description": "<human-readable action description>",
  "origin": "<DID or URI identifying the Requester>",
  "nonce": "<hex-encoded random, min 128 bits>",
  "iat": <unix-timestamp>,
  "exp": <unix-timestamp>,
  "txn": { /* action-specific parameters */ }
}

Normative reference: OID4VP §8.4 states the transaction_data objects are base64url-encoded and their hashes appear in KB-JWT transaction_data_hashes. By defining the JSON structure above, EVES-009 becomes a profile of OID4VP transaction_data — not a custom extension.

Key advantage: Any OID4VP-compliant wallet (EUDI, Sphereon, walt.id) already supports transaction_data. The hash binding is native to the protocol. No custom challenge derivation needed for SD-JWT VPs.


Issue 2 — Composite Nonce Format: Adopt harbour's Delegation Challenge Encoding

Standards basis: harbour-credentials docs/specs/delegation-challenge-encoding.md defines a formal grammar:

delegation-challenge = nonce SP action-type SP hash
nonce               = 8*HEXDIG
action-type         = 1*VCHAR  ; e.g., "HARBOUR_DELEGATE"
hash                = 64HEXDIG ; SHA-256 hex
SP                  = %x20     ; single space

Solution: Adopt this as the normative format for Simple VP (non-SD-JWT) challenge binding. For SD-JWT VPs, the composite nonce is unnecessary because OID4VP transaction_data_hashes in KB-JWT handles binding natively.

Add to §2:

For Simple VPs (VC-JOSE-COSE), the nonce field MUST use the delegation challenge encoding:
<nonce> <action-type> <hash> where fields are space-separated, nonce is at least 16 hex characters (64 bits minimum for the nonce component; see §5), action-type is a non-empty visible-character string, and hash is the 64-character hex-encoded SHA-256 of the canonicalized message.

Rationale from ADR: harbour chose this format to keep the W3C nonce field self-contained — a verifier can extract the hash and validate the binding without external state. The space delimiter was chosen because hex characters and action-type identifiers never contain spaces.


Issue 3 — Nonce Entropy: Follow OID4VP §7.3 + RFC 6749 §10.10

Standards basis:

  • OID4VP §7.3: "The nonce value MUST be a fresh cryptographically random value"
  • RFC 6749 §10.10: Authorization codes should have "sufficient entropy to make guessing impractical" (recommends 128+ bits)
  • RFC 9901 KB-JWT: nonce is REQUIRED and must be fresh per interaction

Solution: Add normative requirement to §5:

The message nonce MUST contain at least 128 bits of cryptographic randomness (for example, secrets.token_hex(16) producing 32 hex characters). For the delegation challenge encoding (Simple VPs), the nonce component within the composite string MUST independently contain at least 64 bits of randomness (16 hex characters minimum). These minimums follow the entropy guidance of RFC 6749 §10.10 and OID4VP §7.3.

Note: harbour's current secrets.token_hex(4) (32 bits) should be updated to token_hex(16) to comply. This is a breaking change for the implementation but necessary for security.


Issue 4 — Revocation: Reference Token Status List (draft-ietf-oauth-status-list)

Standards basis: docs/specs/references/token-status-list.md documents the Token Status List mechanism:

  • Status List Token: JWT/CWT containing a compressed bitstring of credential statuses
  • Referenced via credentialStatus property in VCs
  • Validation: fetch status list token → verify signature → check bit at referenced index
  • Supports VALID, INVALID, SUSPENDED states

Solution: Add step 2b to §4.2 and reference the standard:

2b. Credential status check (RECOMMENDED): For each VC that includes a credentialStatus property, the Verifier SHOULD resolve the referenced status mechanism (for example, Token Status List) and confirm the credential has not been revoked or suspended. If status checking is performed and a credential is found to be revoked, the evidence MUST be considered invalid.

Rationale: harbour's credential model (HarbourCredential) already has credentialStatus as a REQUIRED field. The Token Status List is the IETF-track mechanism adopted by the EUDI ecosystem.


Issue 5 — Evidence Envelope: Use VC Data Model 2.0 §5.6 evidence

Standards basis: VC Data Model 2.0 §5.6 defines:

  • evidence: OPTIONAL property, 0..* objects
  • Each object MUST have type
  • Arbitrary additional properties allowed
  • Purpose: "information about the process by which the credential was issued or derived"

Solution: Define a normative evidence type for EVES-009. Add §7 "Evidence Packaging":

When evidence is embedded within a Verifiable Credential, implementations MUST use the evidence property as defined in VC Data Model 2.0 §5.6. Each evidence object MUST include:

{
  "type": "EvidenceVerifiablePresentation",
  "verifiablePresentation": "<compact-serialized VP token>",
  "challenge": "<delegation-challenge or transaction_data_hash>",
  "created": "<ISO 8601 timestamp>"
}

The verifiablePresentation value is the compact serialization of the VP JWT (for JOSE VPs) or the full SD-JWT+KB-JWT string (for SD-JWT VPs). The challenge value enables independent re-verification without reconstructing the original message.

Alternatively, evidence MAY be stored standalone. In that case, the storage record SHOULD include the original message, the VP token, the Holder DID, and a timestamp.

Alignment with simpulse-id-credentials: This matches the existing pattern where ParticipantCredential is referenced in BaseMembershipCredential.evidence[], and the harbourCredential IRI provides a chain-of-trust reference.


Issue 6 — Time-Bounding: Follow OID4VP §7.4 + KB-JWT iat/exp

Standards basis:

  • OID4VP §7.4: Authorization Requests have a lifetime; Verifiers should reject stale requests
  • RFC 9901 KB-JWT: MUST include iat; MAY include exp
  • harbour-credentials: validate_transaction_data() enforces max_age=300 (5 minutes) against iat

Solution: Add informative guidance to §5:

Evidence requests SHOULD be time-bounded. The message iat (issued-at) timestamp indicates creation time. Implementations SHOULD reject evidence requests older than a configured maximum age. A default of 300 seconds (5 minutes) is RECOMMENDED, balancing security against wallet interaction latency.

For SD-JWT VPs, the KB-JWT iat claim provides an additional time anchor. Verifiers MAY reject KB-JWTs with iat values that deviate significantly from expected transaction time. If the message includes exp (expiration), the Holder's wallet SHOULD reject the request if the current time exceeds exp.

These constraints align with OID4VP §7.4 (request lifetime) and RFC 9901 §4.2 (KB-JWT temporal claims).


Issue 7 — Action Type Registry: Define Namespaced Convention

Standards basis:

  • OID4VP transaction_data objects use a type field to distinguish transaction types
  • harbour uses HARBOUR_DELEGATE (screaming-case constant)
  • simpulse uses credential.issue (dotted namespace)

Solution: Standardize on dotted reverse-domain convention. Add to §1:

Action types identify the class of operation being consented to. Action types MUST be non-empty strings using dotted notation with a namespace prefix to avoid collisions:

Action Type Description
envited-x.credential.issue Issue a credential on the Holder's behalf
envited-x.asset.list List an asset on-chain
envited-x.contract.sign Finalize a contract

Custom action types SHOULD use an organization-owned namespace (for example, com.example.custom-action). The ENVITED-X namespace (envited-x.*) is reserved for actions defined in EVES specifications.

Backwards compatibility note: harbour's HARBOUR_DELEGATE would map to a generic delegation action. Implementations SHOULD support both the legacy format and the namespaced format during a transition period.


Issue 8 — Error Model: Align with OID4VP §6.3 Error Response

Standards basis: OID4VP §6.3 defines error responses for VP submission failures:

  • invalid_request, invalid_scope, access_denied, server_error
  • Each with error_description for human-readable detail

Solution: Define verification error codes in §4.2 aligned with OID4VP conventions:

When verification fails, implementations SHOULD report one of the following error conditions:

Error Code Condition
vp_signature_invalid VP signature does not verify against Holder's DID document
vc_signature_invalid A VC's issuer signature is invalid
vc_requirement_unmet Presented VCs do not satisfy the policy requirements
challenge_mismatch VP is not bound to the claimed message (nonce or transaction_data_hashes mismatch)
sd_hash_mismatch KB-JWT sd_hash does not match the presented SD-JWT (RFC 9901 §4.3.1)
holder_mismatch VP subject does not match expected Holder
credential_revoked A presented VC has been revoked or suspended
evidence_expired Evidence request exceeded maximum age

Error codes follow the snake_case convention of OID4VP §6.3. Implementations MAY extend this set with prefixed custom codes (for example, envited-x.custom_error).


Issue 9 — SD-JWT Flow Diagram: Add OID4VP Transaction Data Sequence

Standards basis: OID4VP §8.4 + Appendix B.3.3 define the full flow:

  1. Verifier includes transaction_data (base64url JSON array) in Authorization Request
  2. Wallet displays transaction to user, collects consent
  3. Wallet creates KB-JWT with transaction_data_hashes = array of base64url(SHA-256(transaction_data[i]))
  4. VP response includes KB-JWT binding

Solution: Add a second mermaid diagram after the existing one:

sequenceDiagram
    participant R as Requester
    participant H as Holder (Wallet)

    R->>R: Construct TransactionData + policy
    R->>R: td_param = base64url(canonical(TransactionData))
    R->>H: OID4VP request (nonce=random, transaction_data=[td_param], credential request)
    H->>H: Decode transaction_data, display action
    H->>H: Select VCs, review and consent
    H->>H: Create SD-JWT VP + KB-JWT(nonce=random, sd_hash, transaction_data_hashes=[hash(td_param)])
    H->>R: POST vp_token (direct_post)
    R->>R: Verify KB-JWT bindings + store evidence
Loading

Note: In the SD-JWT flow, the nonce is a random value for replay prevention only. Message binding is achieved through transaction_data_hashes in the KB-JWT, following OID4VP Appendix B.3.3.


Cross-Reference Matrix: Standards → EVES-009 Sections

Standard Relevant Section EVES-009 Impact
OID4VP §8.4 (transaction_data) §2, §3.2, §4.2 TransactionData structure, SD-JWT binding
OID4VP §7.3 (nonce freshness) §5 Nonce entropy minimum
OID4VP §6.3 (error responses) §4.2 Error model
RFC 9901 §4.2 (KB-JWT claims) §4.2, §5 sd_hash verification, time-bounding
RFC 9901 §4.3.1 (sd_hash) §4.2 check 4 Already referenced ✅
RFC 8785 (JCS) §2 Already referenced ✅
VC Data Model 2.0 §5.6 (evidence) New §7 Evidence packaging
Token Status List (IETF draft) §4.2 step 2b Revocation checking
VC-JOSE-COSE (cnf/PoP) §4.2 check 1 Holder binding via cnf key
harbour ADR-003 (canonicalization) §2, §5 Justification for JCS over RDF canonicalization
harbour delegation-challenge-encoding §2 Composite nonce grammar

Implementation Alignment Notes

Issue harbour-credentials simpulse-id-credentials Spec Change Needed
TransactionData schema TransactionData class with action,nonce,iat,exp,txn Extends with credentialId in txn Define normative profile
Composite nonce <nonce> HARBOUR_DELEGATE <hash> Uses harbour's format Formalize grammar
Nonce entropy token_hex(4) = 32 bits ⚠️ Inherits from harbour ⚠️ Mandate 128 bits
Revocation Not checked in VP verification credentialStatus required in model Add RECOMMENDED step
Evidence envelope VP JWT stored in evidence chain evidence[] with VC references Define EvidenceVerifiablePresentation type
Time-bounding max_age=300 in validate_transaction_data() Inherits Document 5-min default
Action types HARBOUR_DELEGATE credential.issue Standardize dotted namespace
Error codes Python ValueError with messages Inherits Define normative codes
SD-JWT diagram Full KB-JWT flow implemented Uses harbour's flow Add diagram to spec

@jdsika
Copy link
Copy Markdown
Contributor

jdsika commented May 18, 2026

Alignment Check: Proposed Solutions vs LinkML Models & JSON Examples

Cross-referencing the standards-based solutions against the actual data models in harbour-credentials (LinkML + examples) and simpulse-id-credentials.


Issue 1 — TransactionData Structure

What the LinkML model actually defines (harbour-core-delegation.yaml):

TransactionData:
  attributes:
    credential_ids:   # required, multivalued URIs
    nonce:            # required, string
    iat:              # required, integer (unix timestamp)
    exp:             # optional, integer
    description:     # optional, string
    transaction_data_hashes_alg:  # multivalued, defaults to "sha-256"

Action-specific subclasses exist as typed classes with class URIs:

  • CredentialIssueTransactionharbour.delegate:credential.issue
  • DataPurchaseTransactionharbour.delegate:data.purchase
  • BlockchainTransferTransactionharbour.delegate:blockchain.transfer
  • BlockchainExecuteTransactionharbour.delegate:blockchain.execute
  • ContractSignTransactionharbour.delegate:contract.sign

My proposed schema used a flat action field — this conflicts with the model.

✏️ Corrected recommendation: EVES-009 should define the TransactionData structure to match the LinkML model's inheritance pattern:

{
  "@type": "harbour.delegate:credential.issue",
  "credential_ids": ["did:web:envited-x.net:cred:123"],
  "nonce": "a1b2c3d4e5f6a7b8a1b2c3d4e5f6a7b8",
  "iat": 1716024590,
  "exp": 1716024890,
  "description": "Issue ASCS membership credential",
  "transaction_data_hashes_alg": ["sha-256"]
}

The action type is carried via @type (JSON-LD typing), not a flat string field. This aligns with:

  • The LinkML class_uri pattern for polymorphic dispatch
  • VC Data Model's JSON-LD @type convention
  • OID4VP transaction_data objects (which are typed JSON)

Issue 2 — Composite Nonce Format

Model alignment: ✅ Compatible. The nonce field in TransactionData is required. The composite delegation challenge (<nonce> HARBOUR_DELEGATE <hash>) is constructed from the TransactionData nonce — it's a derived value placed in the VP, not stored in the model itself.

No change needed.


Issue 3 — Nonce Entropy

Model alignment: ✅ Compatible. LinkML defines nonce as string with no length constraint — length/entropy is a runtime validation concern, not a schema concern. The spec can mandate 128 bits without conflicting with the model.

No change needed.


Issue 4 — Revocation / Credential Status

What the model actually uses (harbour-core-credential.yaml):

HarbourCredential:
  attributes:
    credentialStatus:
      required: true
      range: CRSetEntry

CRSetEntry:
  attributes:
    statusPurpose:          # e.g., "revocation"
    statusServiceOperator:  # DID of operator
    statusIndex:            # integer index
    statusId:              # optional URI

My proposed solution referenced Token Status List (draft-ietf-oauth-status-list). These are related but not identical:

  • Token Status List uses: statusListCredential (URI to fetch) + statusListIndex
  • Harbour's CRSetEntry uses: statusServiceOperator (DID) + statusIndex

The pattern is conceptually the same (index into a published status list), but the field names and resolution mechanism differ.

✏️ Corrected recommendation: §4.2 should be format-agnostic:

2b. Credential status check (RECOMMENDED): For each VC that includes a credentialStatus property, the Verifier SHOULD resolve the credential's status using the mechanism indicated by the status entry type. Supported mechanisms include Token Status List (draft-ietf-oauth-status-list) and operator-managed revocation registries (for example, harbour CRSet). If a credential is found to be revoked or suspended, the evidence MUST be considered invalid.

This accommodates both Token Status List AND harbour's CRSetEntry without prescribing one over the other.


Issue 5 — Evidence Envelope

What the model actually defines:

harbour LinkML (harbour-core-credential.yaml):

CredentialEvidence:
  attributes:
    verifiablePresentation:   # required, string (compact JWT)

DelegatedSignatureEvidence:
  is_a: CredentialEvidence
  attributes:
    verifiablePresentation:   # required
    delegatedTo:              # required, DID
    transaction_data:         # required, TransactionData
    challenge:                # required, string (delegation challenge)

JSON examples use:

{
  "evidence": [{
    "type": ["harbour:CredentialEvidence"],
    "verifiablePresentation": {
      "@context": ["https://www.w3.org/ns/credentials/v2", "https://w3id.org/reachhaven/harbour/core/v1/"],
      "type": ["VerifiablePresentation", "harbour:VerifiablePresentation"],
      "holder": "did:ethr:0x...",
      "verifiableCredential": ["eyJ..."]
    }
  }]
}

And for delegated signing (delegated-signing-receipt.json):

{
  "evidence": [{
    "type": ["harbour:DelegatedSignatureEvidence"],
    "verifiablePresentation": "eyJ...<compact VP JWT>",
    "delegatedTo": "did:ethr:0x...",
    "transaction_data": { "credential_ids": [...], "nonce": "...", "iat": ... },
    "challenge": "<nonce> HARBOUR_DELEGATE <hash>"
  }]
}

My proposed EvidenceVerifiablePresentation type conflicts with the existing model.

✏️ Corrected recommendation: EVES-009 §7 should reference the existing types:

Evidence MUST be packaged using the evidence property (VC Data Model 2.0 §5.6). Two evidence types are defined:

  • CredentialEvidence (harbour:CredentialEvidence): Contains a verifiablePresentation field holding the evidence VP. Used when the VP alone is sufficient proof of consent.

  • DelegatedSignatureEvidence (harbour:DelegatedSignatureEvidence): Extends CredentialEvidence with:

    • delegatedTo: DID of the party authorized to act
    • transaction_data: The TransactionData object that was consented to
    • challenge: The delegation challenge string binding the VP to the transaction

The verifiablePresentation value is either a nested VP object (for unsigned examples) or a compact JWT string (for signed evidence).


Issue 6 — Time-Bounding

Model alignment: ✅ Compatible.

  • TransactionData.iat (required) + TransactionData.exp (optional) handle request-level timing
  • HarbourCredential.validFrom (required) + validUntil (optional) handle credential-level timing

The 5-minute max_age recommendation is a runtime enforcement on iat, not a schema constraint. No conflict.


Issue 7 — Action Types

What the model actually uses: CURIE-style class URIs:

  • harbour.delegate:credential.issue
  • harbour.delegate:data.purchase
  • harbour.delegate:blockchain.transfer
  • harbour.delegate:contract.sign

My proposed dotted notation (envited-x.credential.issue) is close but not identical.

✏️ Corrected recommendation: Align with the existing CURIE convention:

Action types are expressed as CURIE-style type URIs following the pattern <namespace>:<action>. The harbour.delegate namespace is defined for the core delegation protocol:

Type URI Description
harbour.delegate:credential.issue Issue a credential on the Holder's behalf
harbour.delegate:data.purchase Purchase data asset
harbour.delegate:blockchain.transfer Execute a blockchain transfer
harbour.delegate:blockchain.execute Execute a blockchain smart contract call
harbour.delegate:contract.sign Sign a contract

Domain-specific extensions SHOULD use their own namespace prefix (for example, envited-x.delegate:asset.list). Action types appear as the @type of the TransactionData object, enabling polymorphic validation.


Issue 8 — Error Model

Model alignment: ✅ No conflict. No error enums exist in LinkML (errors are runtime, not data model). The proposed error codes are purely additive.


Issue 9 — SD-JWT Flow Diagram

Model alignment: ✅ Not model-related. The diagram illustrates protocol flow, not data structure.


Summary: Corrections Required

Issue Original Proposal Model Reality Correction
1. TransactionData Flat action field @type class URI + typed subclasses Use @type for action dispatch
4. Revocation Token Status List only CRSetEntry (operator-managed registry) Make format-agnostic, cite both
5. Evidence envelope New EvidenceVerifiablePresentation type Existing harbour:CredentialEvidence + DelegatedSignatureEvidence Use existing type hierarchy
7. Action types Dotted notation envited-x.credential.issue CURIE harbour.delegate:credential.issue Adopt CURIE convention
2, 3, 6, 8, 9 As proposed Compatible No change needed

All four corrections are naming/structural alignment — the underlying standards recommendations (OID4VP §8.4, VC DM 2.0 §5.6, RFC 9901, Token Status List) remain correct. The harbour LinkML model is already a profile of those standards.


Compatibility Verdict

The standards-based solutions are fully compatible with the LinkML models and JSON examples after the four corrections above. Specifically:

  • TransactionData structure matches the existing required fields (credential_ids, nonce, iat) with optional extensions (exp, description)
  • ✅ Evidence packaging reuses existing harbour:CredentialEvidence / DelegatedSignatureEvidence types
  • credentialStatus check works with both Token Status List and harbour's CRSetEntry
  • ✅ Action types fit the existing CURIE harbour.delegate:* namespace
  • ✅ Time-bounding (iat/exp on TransactionData, validFrom/validUntil on credentials) already modeled
  • ✅ Nonce is a required string field — entropy minimum is additive runtime validation
  • ✅ Challenge (<nonce> HARBOUR_DELEGATE <hash>) stored explicitly in DelegatedSignatureEvidence.challenge

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants